// The following ifdef block is the standard way of creating macros which make exporting 
// from a DLL simpler. All files within this DLL are compiled with the GEOMETRY_EXPORTS
// symbol defined on the command line. This symbol should not be defined on any project
// that uses this DLL. This way any other project whose source files include this file see 
// GEOMETRY_API functions as being imported from a DLL, whereas this DLL sees symbols
// defined with this macro as being exported.
#ifdef GEOMETRY_EXPORTS
#define GEOMETRY_API __declspec(dllexport)
#else
#define GEOMETRY_API __declspec(dllimport)
#endif

// This class is exported from the Geometry.dll
class GEOMETRY_API CGeometry {
public:
	CGeometry(void);
	// TODO: add your methods here.
};

extern GEOMETRY_API int nGeometry;

GEOMETRY_API int fnGeometry(void);

#pragma once

#include <math.h>

#define PI 3.14159265
#define INF 100000.0

namespace VectorMath
{
	class GEOMETRY_API Vector3
	{
	private:
		double x, y, z;
	public:
		Vector3(double x=0, double y=0, double z=0)
		{
			this->x = x;
			this->y = y;
			this->z = z;
		}

		void SetX (double newX)
		{
			this->x = newX;
		}

		void SetY (double newY)
		{
			this->y = newY;
		}

		void SetZ (double newZ)
		{
			this->z = newZ;
		}

		double GetX ()
		{
			return this->x;
		}

		double GetY ()
		{
			return this->y;
		}

		double GetZ ()
		{
			return this->z;
		}

		Vector3 operator + (const Vector3 &other)
		{
			return Vector3(this->x + other.x,
				this->y + other.y,
				this->z + other.z);
		}

		Vector3 operator - (const Vector3 &other)
		{
			return Vector3(this->x - other.x,
				this->y - other.y,
				this->z - other.z);
		}

		Vector3 operator * (const double &coef)
		{
			return Vector3(this->x * coef,
				this->y * coef,
				this->z * coef);
		}

		Vector3 operator / (const double &coef)
		{
			return Vector3(this->x / coef,
				this->y / coef,
				this->z / coef);
		}

		double GetLength()
		{
			double xSquared = this->x * this->x,
				ySquared = this->y * this->y,
				zSquared = this->z * this->z;

			return sqrt ( xSquared + 
				ySquared + 
				zSquared);
		}

		double GetSqLength()
		{
			double xSquared = this->x * this->x,
				ySquared = this->y * this->y,
				zSquared = this->z * this->z;

			return xSquared + 
				ySquared + 
				zSquared;
		}

		void Normalize()
		{
			double vLength = this->GetLength();
			Vector3 ourVector = (*this);
			(*this) = ourVector / vLength;
		}

		void RotateInZX (double angleInDegrees)
		{
			double alpha = angleInDegrees * PI/180;
			double resultZ = this->z * cos(alpha) - this->x * sin(alpha);
			double resultX = this->z * sin(alpha) + this->x * cos(alpha);
			this->z = resultZ;
			this->x = resultX;
		}

		void RotateInXY (double angleInDegrees)
		{
			double alpha = angleInDegrees * PI/180;
			double resultX = this->x * cos(alpha) - this->y * sin(alpha);
			double resultY = this->x * sin(alpha) + this->y * cos(alpha);
			this->x = resultX;
			this->y = resultY;
		}

		void RotateInYZ (double angleInDegrees)
		{
			double alpha = angleInDegrees * PI/180;
			double resultY = this->y * cos(alpha) - this->z * sin(alpha);
			double resultZ = this->y * sin(alpha) + this->z * cos(alpha);
			this->y = resultY;
			this->z = resultZ;
		}

		void RotateAroundXAxis (double angleInDegrees)
		{
			this->RotateInYZ (angleInDegrees);
		}

		void RotateAroundYAxis (double angleInDegrees)
		{
			this->RotateInZX (angleInDegrees);
		}

		void RotateAroundZAxis (double angleInDegrees)
		{
			this->RotateInXY (angleInDegrees);
		}
	};

	double DotProduct (Vector3 A, Vector3 B)
	{
		double result = A.GetX() * B.GetX() + A.GetY() * B.GetY() + A.GetZ() * B.GetZ();
		return result;
	}

	double CrossProductXY (Vector3 A, Vector3 B)
	{
		double result = A.GetX() * B.GetY() - A.GetY() * B.GetX();
		return result;
	}

	Vector3 CrossProduct (Vector3 A, Vector3 B)
	{
		Vector3 result;
		result.SetX ( A.GetY() * B.GetZ() - B.GetY() * A.GetZ() );
		result.SetY ( A.GetZ() * B.GetX() - B.GetZ() * A.GetX() );
		result.SetZ ( A.GetX() * B.GetY() - B.GetX() * A.GetY() );
		return result;
	}
	
	double TriangleArea (Vector3 A, Vector3 B, Vector3 C)
	{
		Vector3 cpResult = CrossProduct ( B-A, C-A );
		return cpResult.GetLength() / 2;
	}

	double TriangleXYArea (Vector3 A, Vector3 B, Vector3 C)
	{
		Vector3 AB = B - A,
			AC = C - A;
		double area = fabs (CrossProductXY (AC, AB));
		area = area / 2;
		return area;
	}

	double GetAngle_rads (Vector3 A, Vector3 B)
	{
		A.Normalize();
		B.Normalize();
		double cosAngle = DotProduct(A, B);
		double angle_rads = acos(cosAngle);
		return angle_rads;
	}

	double GetAngle_degs (Vector3 A, Vector3 B)
	{
		A.Normalize();
		B.Normalize();
		double cosAngle = DotProduct(A, B);
		double angle_rads = acos(cosAngle);
		double angle_degs = angle_rads * 180.0 / PI;
		return angle_degs;
	}

	double GetProjectionLength (Vector3 A, Vector3 base)
	{
		double dpResult = DotProduct(A, base);
		double projectionLength = dpResult / base.GetLength();
		return projectionLength;
	}

	Vector3 PointLineProjection (Vector3 point, Vector3 lineA, Vector3 lineB)
	{
		Vector3 result;
		return result;
	}
}

namespace BoundingBodies
{
	class GEOMETRY_API BoundingSphere
	{
		double radius;
		VectorMath::Vector3 center;
	public:
		BoundingSphere()
		{
			this->center = VectorMath::Vector3();
			this->radius = 0;
		}

		BoundingSphere(VectorMath::Vector3 * vertices, int numVertices)
		{
			for(int i=0; i<numVertices; i++)
			{
				this->center = this->center + vertices[i];
			}
			center = center / numVertices;

			double sqMaxVertexDistance = 0;
			for(int i=0; i<numVertices; i++)
			{
				VectorMath::Vector3 currentDistVector = vertices[i] - this->center;
				double sqCurrentDist = currentDistVector.GetSqLength();
				if(sqMaxVertexDistance < sqCurrentDist)
				{
					sqMaxVertexDistance = sqCurrentDist;
				}
			}
			this->radius = sqrt(sqMaxVertexDistance);
		}
	};

	class GEOMETRY_API AABB
	{
		VectorMath::Vector3 minCorner,
			maxCorner;
	public:
		AABB()
		{
			minCorner = VectorMath::Vector3();
			maxCorner = VectorMath::Vector3();
		}

		AABB(VectorMath::Vector3 * vertices, int numVertices)
		{
			double minX=INF, minY=INF, minZ=INF,
				maxX=-INF, maxY=-INF, maxZ=-INF;

			for(int i=0; i<numVertices; i++)
			{
				double currX = vertices[i].GetX(),
					currY = vertices[i].GetY(),
					currZ = vertices[i].GetZ();

				if(minX > currX)
				{
					minX = currX;
				}
				if(maxX < currX)
				{
					maxX = currX;
				}

				if(minY > currY)
				{
					minY = currY;
				}
				if(maxY < currY)
				{
					maxY = currY;
				}

				if(minZ > currZ)
				{
					minZ = currZ;
				}
				if(maxZ < currZ)
				{
					maxZ= currZ;
				}
			}
			this->minCorner = VectorMath::Vector3(minX, minY, minZ);
			this->maxCorner = VectorMath::Vector3(maxX, maxY, maxZ);
		}
	};
}